#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>


// 定义一个断言宏 (`__debugbreak();` 是MSVC的断言方法)
#define ASSERT(x) if (!(x)) __debugbreak();
// 定义一个GL指令宏: 在执行OpenGLAPI前调用GLClearError函数, 在执行OpenGLAPI后调用ASSERT(GLLogCall())函数
#define GLCALL(x) { GLClearError();\
	x;\
ASSERT(GLLogCall(#x, __FILE__, __LINE__)); }

static void GLClearError() {
	while (glGetError() != GL_NO_ERROR);
}

static bool GLLogCall(const char* functionName, const char* filePath, int line) {
	while (GLenum error = glGetError()) {
		std::cout << "[OpenGL Error]: (" << error << "): " << functionName << filePath << line << std::endl; // OpenGL的错误是16进制的, 这个错误码是10进制的
		return false;
	}
	return true;
}

// Shader程序代码模型
struct ShaderProgramSource {
	std::string VertexSource;
	std::string FragementSource;
};

// 封装函数从文件中读取shader, shader文件预处理函数
static ShaderProgramSource ParseShaderFile(const std::string& filePath) {
	enum class ShaderType {
		NONE = -1,
		VERTEX = 0,
		FRAGMENT = 1,
	};

	// 读取文件内容到line指针
	std::ifstream stream(filePath);

	std::string line;
	std::stringstream ss[2]; // 用于装载shader文件预处理内容模型
	ShaderType type = ShaderType::NONE;
	while (getline(stream, line)) {
		if (line.find("#shader") != std::string::npos) {
			if (line.find("vertex") != std::string::npos) { type = ShaderType::VERTEX; }
			else if (line.find("fragement") != std::string::npos) { type = ShaderType::FRAGMENT; }
		}

		// ! "#shader"
		else {
			ss[(int)type] << line << '\n';
		}
	}
	return { ss[0].str(), ss[1].str() };
}

// 编译shader程序
static unsigned int CompileShader(unsigned int type, const std::string& source) {
	unsigned int id = glCreateShader(type);
	const char* src = source.c_str();
	glShaderSource(id, 1, &src, nullptr); // https://docs.gl/gl4/glShaderSource
	glCompileShader(id);

	// TODO: Error Handling
	int result;
	glGetShaderiv(id, GL_COMPILE_STATUS, &result);
	if (result == GL_FALSE) {
		int length;
		glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
		char* message = (char*)alloca(length * sizeof(char)); // 在堆栈中申请一块内存用于从gl获取错误的编译信息
		glGetShaderInfoLog(id, length, &length, message);
		std::cout << "Failed to copmpile" << (type == GL_VERTEX_SHADER ? "vertex" : "fragment") << " shader" << std::endl;
		std::cout << message << std::endl;
		glDeleteShader(id);
		return 0;
	}

	return id;
}

// 创建shader程序
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader) {
	unsigned int program = glCreateProgram();
	unsigned int vs = CompileShader(GL_VERTEX_SHADER, vertexShader);
	unsigned int fs = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);

	glAttachShader(program, vs);
	glAttachShader(program, fs);
	glLinkProgram(program);
	glValidateProgram(program);

	// 对于shader是否需要清理
	// 1. shader程序体量很小, 不是很需要清理
	// 2. 如果删除了显卡的着色器程序, 那么着色器程序将无法进行调试
	glDeleteShader(vs);
	glDeleteShader(fs);

	return program;
}

int main(void) {

	// 创建GLFW窗体
	GLFWwindow* window;
	if (!glfwInit()) return -1; // 初始化GLFW
	window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL); // 初始化一个窗口并且获取它的OpenGL上下文
	if (!window) {
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window); // Make the window's context current
	glfwSwapInterval(1); // 与主显示器的刷新频率的比值

	// 初始化GLEW
	// GLEW是一个跨平台的OpenGL封装库, 用于整合多平台OpenGLAPI的使用
	// 根据文档所述GLEW初始化必须在创建完一个OpenGL上下文后进行
	// https://glew.sourceforge.net/basic.html -> Initializing GLEW
	// GLEW初始化阶段需要在C++预处理器中传入 Processor Definitions -> "GLEW_STATIC"
	GLenum err = glewInit();
	if (GLEW_OK != err) { std::cout << err << std::endl; }
	std::cout << glGetString(GL_VERSION) << std::endl; // 通过GLEW调用OpenGLAPI实现跨平台的目的

	// (1) 创建/设置顶点缓冲区 VRAM
	// 创建顶点索引缓冲区
	unsigned int indcies[] = { 0, 1, 2, 2, 3, 0 };
	unsigned int ibo; // IndexBufferObject
	glGenBuffers(1, &ibo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); // 使用API给状态机绑定当前操作的buffer
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indcies, GL_STATIC_DRAW); // 给buffer添加数据

	// 创建顶点的位置属性的缓冲区
	float vertexPosisitons[] = {
		-0.5f, -0.5f, // 0
		 0.5f, -0.5f, // 1
		 0.5f, 0.5f,  // 2
		-0.5f, 0.5f,  // 3
	};
	unsigned int vertexBuffer; // 定义一个无符号整形buffer
	glGenBuffers(1, &vertexBuffer); // 使用API创建OpenGLBuffer
	glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); // 使用API给状态机绑定当前操作的buffer
	glBufferData(GL_ARRAY_BUFFER, 4 * 2 * sizeof(float), vertexPosisitons, GL_STATIC_DRAW); // 给buffer添加数据

	// (2) 对于顶点的属性进行指定缓冲区布局分配 setting layout of buffers
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0);
	// glVertexAttribPointer API 参数详细解释
	// index 属性顺序
	// size 大小
	// type 类型
	// if_normalize 是否01标准化
	// stride 该顶点attributes占用buffer大小
	// float 偏移

	// (3) 编写/链接/编译 显卡着色器程序
	ShaderProgramSource source = ParseShaderFile("resources/shaders/HelloUniform.shader"); // properties -> DEBUG -> Working Directory VisualStudio默认不是main函数位置, 而是项目位置
	// 打印输出一下通过函数预处理之后的文件内容
	std::cout << "Vertex Shader Source: " << std::endl;
	std::cout << source.VertexSource << std::endl;
	std::cout << std::endl;
	std::cout << "Fragment Shader Source: " << std::endl;
	std::cout << source.FragementSource << std::endl;
	unsigned int shader = CreateShader(source.VertexSource, source.FragementSource);
	glUseProgram(shader); // 调用API使用程序

	// Uniforms
	int location = glGetUniformLocation(shader, "u_Color");
	ASSERT(location != -1);
	glUniform4f(location, 1.0f, 0.0f, 0.0f, 1.0f); // 初始化为红色
	float r = 0.0f;
	float increament = 0.05f;

	// 重复渲染循环直到关闭了窗口 Render here
	while (!glfwWindowShouldClose(window)) {
		glClear(GL_COLOR_BUFFER_BIT); // Clear buffers

		// (4) 执行DrawCall, CPU通知GPU使用着色器程序绘制
		// https://docs.gl/gl4/glDrawElements
		glUniform4f(location, r, 0.0f, 0.0f, 1.0f); // 动态改变Uniform值
		GLCALL(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr));

		if (r > 1.0f) { increament = -0.05f; }
		else if (r < 0.0f) { increament = 0.05f; }
		r += increament;

		glfwSwapBuffers(window); // Swap front and back buffers
		glfwPollEvents(); // Poll for and process events
	}

	// (5) 在窗口关闭后从显卡分配内存中删除shader源码
	glDeleteProgram(shader);

	glfwTerminate();
	return 0;
}